
`include "common_header.verilog"

//  *************************************************************************
//  File : pause_tx_trigctl
//  *************************************************************************
//  This program is controlled by a written license agreement.
//  Unauthorized Reproduction or Use is Expressly Prohibited. 
//  Copyright (c) 2007 MoreThanIP.com, Germany
//  Designed by : Daniel Koehler
//  info@morethanip.com
//  *************************************************************************
//               10G/32 Ethernet MAC Core 
//  *************************************************************************
//  Description: Determine when and what type of pause frame (XON/XOFF) should
//               be sent.
//  Version    : $Id: pause_tx_trigctl.v,v 1.3 2010/10/08 14:59:20 dp Exp $
//  *************************************************************************

module pause_tx_trigctl (
   reset_txclk,
   xgmii_txclk,
   xgmii_txclk_ena,
   qtime,
   p_enable,
   tx_pfc_xoff,
   pause_quanta,
   pause_thresh,
   pause_quanta_o,
   pause_cena_o,
   pause_req,
   qtime_run,
   pause_busy);

input   reset_txclk;            //  Active High reset for xgmii_txclk domain
input   xgmii_txclk;            //  XGMII transmit clock      
input   xgmii_txclk_ena;        //  XGMII Transmit Clock p_enable.
input   qtime;                  //  pulse when pause time of 1 quanta expired (512 bits)
input   p_enable;               //  enable pause generation, else reset all
input   tx_pfc_xoff;            //  Transmit Flow Control Generate (congestion indication)
input   [15:0] pause_quanta;    //  quanta to use for xoff frames from config
input   [15:0] pause_thresh;    //  threshold for refresh pause frame from config
output  [15:0] pause_quanta_o;  //  quanta to use for transmission now (network byte order)
output  pause_cena_o;           //  class enable to use for transmission now
output  pause_req;              //  request transmission of pause frame
output  qtime_run;              //  quanta timer should run
input   pause_busy;             //  pause frame is currently generated

reg     [15:0] pause_quanta_o; 
reg     pause_cena_o; 
reg     pause_req; 
wire    qtime_run; 

// TYPE stm_typ:
parameter STM_TYP_IDLE          = 3'd 0;
parameter STM_TYP_PAUSE_WAIT    = 3'd 1;
parameter STM_TYP_PAUSE_START   = 3'd 2;
parameter STM_TYP_XOFF_START    = 3'd 3;
parameter STM_TYP_XOFF_WAIT     = 3'd 4;
parameter STM_TYP_XON_WAIT      = 3'd 5;
parameter STM_TYP_XON_START     = 3'd 6;

reg     [2:0] state; 
reg     [2:0] nextstate; 
wire    dec_ok;         //  allow decrementing the timer
wire    tload;          //  load timer with given val
wire    twait;          //  wait for timer, it is non-zero
wire    thresh_low;     //  counter below threshold
reg     xoff_d1;        //  transmit flow control generate (delay 1)
reg     xoff_d2;        //  transmit flow control generate (delay 2)

assign dec_ok = 1'b 1; 

//  The refresh timer
//  -----------------
pause_tmr_th U_T (
          .reset(reset_txclk),
          .clk(xgmii_txclk),
          .clk_ena(1'b 1),
          .tmr_enable(p_enable),
          .qtime(qtime),
          .dec_ok(dec_ok),
          .quanta_val(pause_quanta),
          .tload(tload),
          .twait(twait),
          .qthreshold(pause_thresh),
          .thresh_low(thresh_low));

//  STM
//  ---
always @(posedge reset_txclk or posedge xgmii_txclk)
   begin : pstmr
   if (reset_txclk == 1'b 1)
      begin
      state <= STM_TYP_IDLE;	
      end
   else
      begin
      if (p_enable == 1'b 0)
         begin
         state <= STM_TYP_IDLE;	
         end
      else if (xgmii_txclk_ena == 1'b 1 )
         begin
         state <= nextstate;	
         end
      end
   end

//  STM
//  ---
always @(state or p_enable or xoff_d2 or pause_busy or thresh_low)
   begin : pstm
   case (state)
   STM_TYP_IDLE:
      begin
      if (p_enable == 1'b 1 & xoff_d2 == 1'b 1 & pause_busy == 1'b 0)
         begin
         nextstate = STM_TYP_PAUSE_WAIT;	
         end
      else
         begin
         nextstate = STM_TYP_IDLE;	
         end
      end

   STM_TYP_PAUSE_WAIT:
      begin
      //  wait for pause_busy=0 if other request is currently being served
      if (pause_busy == 1'b 0)
         begin
         nextstate = STM_TYP_PAUSE_START;	
         end
      else
         begin
         nextstate = STM_TYP_PAUSE_WAIT;	
         end
      end

//  trigger frame transmission and wait until it is sent
//  ----------------------------------------------------                                        
   STM_TYP_PAUSE_START:
      begin
      if (pause_busy == 1'b 1 & xoff_d2 == 1'b 1)
         begin
         //  XOFF frame is being sent now, continue
         nextstate = STM_TYP_XOFF_START;	
         end
      else if (pause_busy == 1'b 1 & xoff_d2 == 1'b 0 )
         begin
         //  XOFF request cancelled, XON frame is being sent now, continue
         nextstate = STM_TYP_IDLE;	
         end
      else
         begin
         nextstate = STM_TYP_PAUSE_START;	
         end
      end

   STM_TYP_XOFF_START:
      begin
      //  wait for thresh_low update
      nextstate = STM_TYP_XOFF_WAIT;	
      end

//  wait for refresh timer or congestion clear
//  ----------------------
   STM_TYP_XOFF_WAIT:
      begin
      if (pause_busy == 1'b 0 & xoff_d2 == 1'b 1 & 
	thresh_low == 1'b 1)
         begin
         //  send a refresh frame 
         nextstate = STM_TYP_PAUSE_WAIT;	
         end
      else if (pause_busy == 1'b 0 & xoff_d2 == 1'b 0 )
         begin
         //  send a XON frame as congestion cleared
         nextstate = STM_TYP_XON_WAIT;	
         end
      else
         begin
         nextstate = STM_TYP_XOFF_WAIT;	
         end
      end

   STM_TYP_XON_WAIT:
      begin
      //  wait for pause_busy=0 if other request is currently being served
      if (pause_busy == 1'b 0)
         begin
         nextstate = STM_TYP_XON_START;	
         end
      else
         begin
         nextstate = STM_TYP_XON_WAIT;	
         end
      end

//  trigger frame transmission and wait until it is sent
//  ----------------------------------------------------                                        
   STM_TYP_XON_START:
      begin
      if (pause_busy == 1'b 1 & xoff_d2 == 1'b 0)
         begin
         //  XON frame is being sent now, continue
         nextstate = STM_TYP_IDLE;	
         end
      else if (pause_busy == 1'b 1 & xoff_d2 == 1'b 1 )
         begin
         //  XOFF frame is being sent now, continue
         nextstate = STM_TYP_XOFF_START;	
         end
      else
         begin
         nextstate = STM_TYP_XON_START;	
         end
      end

   default:
      begin
      nextstate = STM_TYP_IDLE;	//  never reached
      end
   endcase
   end

//  Control
//  -------        
always @(posedge reset_txclk or posedge xgmii_txclk)
   begin : pctl
   if (reset_txclk == 1'b 1)
      begin
      xoff_d1 <= 1'b 0;	
      xoff_d2 <= 1'b 0;	
      pause_quanta_o <= {16{1'b 0}};	
      pause_cena_o <= 1'b 0;	
      pause_req <= 1'b 0;	
      end
   else
      begin
      if (xgmii_txclk_ena == 1'b 1)
         begin
         xoff_d1 <= tx_pfc_xoff;	
         xoff_d2 <= xoff_d1;	

        //  determine if we must send XOFF or XON frame and set quanta accordingly
        //  produce it in correct network byte order immediately.
        //  ------------------------------
         if ((nextstate == STM_TYP_PAUSE_WAIT | nextstate == STM_TYP_XON_WAIT) & 
              pause_busy == 1'b 0 & tx_pfc_xoff == 1'b 1 | 
              (nextstate == STM_TYP_PAUSE_START | nextstate == STM_TYP_XON_START) & 
              tx_pfc_xoff == 1'b 1 | nextstate == STM_TYP_XOFF_START)
            begin
            pause_quanta_o <= {pause_quanta[7:0], pause_quanta[15:8]};	
            end
         else if ((nextstate == STM_TYP_PAUSE_WAIT | nextstate == STM_TYP_XON_WAIT) & 
                   pause_busy == 1'b 0 & tx_pfc_xoff == 1'b 0 | 
                   (nextstate == STM_TYP_PAUSE_WAIT | nextstate == STM_TYP_XON_WAIT) & 
                   pause_busy == 1'b 1 | (nextstate == STM_TYP_PAUSE_START | 
                   nextstate == STM_TYP_XON_START) & tx_pfc_xoff == 1'b 0 | 
                   nextstate == STM_TYP_XOFF_WAIT & pause_busy == 1'b 0 | 
                   nextstate == STM_TYP_IDLE )
            begin
            pause_quanta_o <= {16{1'b 0}};	
            end

         if ((nextstate == STM_TYP_PAUSE_WAIT | nextstate == STM_TYP_XON_WAIT) & 
              pause_busy == 1'b 0 | nextstate == STM_TYP_PAUSE_START | 
              nextstate == STM_TYP_XON_START)
            begin
            pause_cena_o <= 1'b 1;	
            end
         else if ((nextstate == STM_TYP_PAUSE_WAIT | nextstate == STM_TYP_XON_WAIT) & 
                   pause_busy == 1'b 1 | (nextstate == STM_TYP_XOFF_WAIT | 
                   nextstate == STM_TYP_IDLE) & pause_busy == 1'b 0 )
            begin
            //  clear cena when frame has been sent
            pause_cena_o <= 1'b 0;	
            end

        //  indicate we want to send a pause frame
        //  --------------------------------------
         if (nextstate == STM_TYP_PAUSE_WAIT | nextstate == STM_TYP_PAUSE_START | 
             nextstate == STM_TYP_XON_WAIT | nextstate == STM_TYP_XON_START)
            begin
            pause_req <= 1'b 1;	
            end
         else
            begin
            pause_req <= 1'b 0;	
            end
         end
      end
   end

//  load refresh timer and keep it loaded until the pause frame is being transmitted
assign tload = state == STM_TYP_PAUSE_START | state == STM_TYP_XON_START ? 1'b 1 : 
	1'b 0; 

//  indicate the quanta timer should run
assign qtime_run = state == STM_TYP_IDLE ? 1'b 0 : 
	1'b 1; 

endmodule // module pause_tx_trigctl

